#include "Path.hpp"
#include "UnitTest.hpp"
#include "Trace.hpp"
#include <string.h>

Path::Path()
{
	absolute = false;
}

Path::Path(string str)
{
	initFromStr(str.c_str());
}

Path::Path(const char *str)
{
	initFromStr(str);
}

void Path::initFromStr(const char *str)
{
	if(!str || !*str) {
		absolute = false;
		return;
	}
	
	// Separate path into components
	int ii;
	
	if(str[0]=='/') {
		absolute = true;
		ii = 1;
	} else {
		absolute = false;
		ii = 0;
	}
	
	int componentStart = ii;
	int pathLen = strlen(str);
	for(; ii<pathLen; ii++)
	{
		if(str[ii]=='/') {
			components.push_back(string(str+componentStart, ii-componentStart));
			componentStart = ii+1;
		}
	}
	if(ii>componentStart)
		components.push_back(string(str+componentStart, ii-componentStart));
	
	canonicalize();
}

void Path::canonicalize()
{
	// Cancel out components
	bool *cancelled = new bool[components.size()];
	// '.' components don't do anything (in canonicalized paths, it's only a
	// placeholder for empty paths).
	for(unsigned ii=0; ii<components.size(); ii++) {
		if(components[ii]==".") {
			cancelled[ii] = true;
		} else {
			cancelled[ii] = false;
		}
	}
	// Each '..' component cancels out the last uncancelled preceding
	// component, and if there is such a component, is cancelled itself.
	// If there is nothing for it to cancel with, cancel it only if the path
	// is absolute ("/../foo"=="/foo", but "../foo"!="foo").
	for(int ii=0; ii<(int)components.size(); ii++)
	{
		if(components[ii]=="..")
		{
			// Go backwards looking for something to cancel with
			for(int jj=ii-1; jj>=0; jj--)
			{
				if(!cancelled[jj] && components[jj]!="..") {
					cancelled[ii] = true;
					cancelled[jj] = true;
					break;
				}
			}
			
			// If nothing found to cancel with, cancel anyways iff it's an
			// absolute path
			if(absolute) {
				cancelled[ii] = true;
			}
		}
	}
	
	vector<string> newComponents;
	for(unsigned ii=0; ii<components.size(); ii++) {
		if(!cancelled[ii])
			newComponents.push_back(components[ii]);
	}
	components = newComponents;
	
	delete[] cancelled;
}

string Path::toString() const
{
	if(!components.size()) {
		if(absolute) return "/";
		else return ".";
	}
	
	string ret;
	if(absolute) ret = "/";
	else ret = "";
	
	// FIXME: Do this in a non-O(n^2) way
	for(unsigned ii=0; ii<components.size(); ii++)
	{
		if(ii)
			ret += "/";
		ret += components[ii];
	}
	
	return ret;
}

bool Path::isAbsolute() const
	{ return absolute; }
bool Path::isRelative() const
	{ return !absolute; }

int Path::numComponents() const
{
	return components.size();
}

string Path::getComponent(int component) const
{
	return components[component];
}

Path Path::parent() const
{
	Path ret;
	if(absolute)
	{
		ret.absolute = true;
		if(components.size() > 0)
			ret.components.insert(ret.components.end(), components.begin(), components.end()-1);
		return ret;
	}
	else
	{
		ret.absolute = false;
		if(!components.size() || components[components.size()-1]=="..") {
			if(components.size())
				ret.components.insert(ret.components.end(), components.begin(), components.end());
			ret.components.push_back("..");
		}
		else
		{
			ret.components.insert(ret.components.end(), components.begin(), components.end()-1);
		}
		return ret;
	}
}

Path Path::relativize(const Path &base) const
{
	// Both paths must be absolute for this to make sense
	// If not, just return the original relative path
	if(isRelative() || base.isRelative())
		return *this;
	
	// Find the largest common prefix
	int prefixLen = 0;
	int shortestLen = min(components.size(), base.components.size());
	for(int ii=0; ii<shortestLen; ii++)
	{
		if(components[ii] == base.components[ii])
			prefixLen = ii+1;
		else
			break;
	}
	
	// Emit a '..' for each directory in the base not in the common prefix,
	Path ret;
	for(unsigned ii=0; ii<base.components.size()-prefixLen; ii++)
		ret.components.push_back("..");
	// Emit each directory in this not in the prefix.
	ret.components.insert(ret.components.end(), components.begin()+prefixLen, components.end());
	
	return ret;
}

Path Path::resolve(const Path &base) const
{
	if(!isRelative())
		return *this;
	
	Path ret;
	ret.absolute = base.absolute;
	ret.components.insert(ret.components.end(), base.components.begin(), base.components.end());
	ret.components.insert(ret.components.end(), components.begin(), components.end());
	ret.canonicalize();
	
	return ret;
}

Path Path::toLocalURI() const
{
	// TODO
	return Path("");
}

string Path::basename() const
{
	if(components.size())
		return components[components.size()-1];
	else
		return "";
}

string Path::dirname() const
{
	if(!components.size())
	{
		if(absolute)
			return "/";
		else
			return ".";
	}
	
	Path ret;
	ret.absolute = absolute;
	ret.components.insert(ret.components.end(), components.begin(), components.end()-1);
	return ret.toString();
}

string Path::extension() const
{
	if(!components.size())
		return "";
	string lastComponent = components[components.size()-1];
	
	// Find the last '.' in the last component.
	for(int ii=lastComponent.length()-1; ii>=0; ii--) {
		char ch = lastComponent[ii];
		if(ch== '.') {
			return lastComponent.substr(ii, lastComponent.length()-ii);
		} else if(ch=='/') {
			return "";
		}
	}
	return "";
}

TEST_SET(Path)
{
	testAssert(Path("").toString() == ".");
	testAssert(Path(".").toString() == ".");
	testAssert(Path("..").toString() == "..");
	testAssert(Path("foo").toString() == "foo");
	testAssert(Path("foo/.").toString() == "foo");
	testAssert(Path("./.").toString() == ".");
	testAssert(Path("./foo").toString() == "foo");
	testAssert(Path("foo/..").toString() == ".");
	testAssert(Path("foo/bar/../..").toString() == ".");
	testAssert(Path("foo/../bar/..").toString() == ".");
	testAssert(Path("foo/../../bar/123").toString() == "../bar/123");
	testAssert(Path("foo/../..").toString() == "..");
	testAssert(Path("../foo").toString() == "../foo");
	
	testAssert(Path("/foo").toString() == string("/foo"));
	
	testAssert(Path("foo/bar").isRelative());
	testAssert(!Path("/foo/bar").isRelative());
	
	testAssert(Path("..").parent().toString()=="../..");
	testAssert(Path("../..").parent().toString()=="../../..");
	testAssert(Path("../foo").parent().toString()=="..");
	testAssert(Path("foo").parent().toString()==".");
	testAssert(Path("/foo").parent().toString()=="/");
	testAssert(Path("/foo/bar").parent().toString()=="/foo");
	
	testAssert(Path("/").relativize("/foo").toString() == "..");
	testAssert(Path("/foo").relativize("/bar").toString() == "../foo");
	testAssert(Path("/foo/bar").relativize("/").toString() == "foo/bar");
	testAssert(Path("/foo/bar/xyz").relativize("/").toString() == "foo/bar/xyz");
	testAssert(Path("/foo/bar").relativize("/foo").toString() == "bar");
	testAssert(Path("/foo").relativize("/foo/bar").toString() == "..");
	testAssert(Path("/foo").relativize("/foo").toString() == ".");
	
	testAssert(Path("/foo").resolve("/bar").toString() == "/foo");
	testAssert(Path("foo").resolve("bar").toString() == "bar/foo");
	testAssert(Path("foo").resolve("/bar").toString() == "/bar/foo");
	testAssert(Path("foo").resolve("..").toString() == "../foo");
	testAssert(Path("foo/bar").resolve("..").toString() == "../foo/bar");
	testAssert(Path("../foo").resolve("..").toString() == "../../foo");
	testAssert(Path("..").resolve("foo").toString() == ".");
	testAssert(Path("../..").resolve("foo").toString() == "..");
	testAssert(Path("../..").resolve("foo/bar").toString() == ".");
	
	testAssert(Path("foo").basename() == "foo");
	testAssert(Path("/foo.xyz").basename() == "foo.xyz");
	testAssert(Path("../foo").basename() == "foo");
	testAssert(Path("../foo/bar.xyz").basename() == "bar.xyz");
	testAssert(Path(".").basename() == "");
	testAssert(Path("..").basename() == "..");
	
	testAssert(Path("foo").dirname() == ".");
	testAssert(Path("../foo").dirname() == "..");
	testAssert(Path(".").dirname() == ".");
	testAssert(Path("..").dirname() == ".");
	testAssert(Path("../..").dirname() == "..");
	testAssert(Path("/").dirname() == "/");
	testAssert(Path("/foo").dirname() == "/");
	testAssert(Path("/foo/bar").dirname() == "/foo");
	
	testAssert(Path("foo.bar").extension() == ".bar");
	testAssert(Path("/foo.bar").extension() == ".bar");
	testAssert(Path("../foo.bar").extension() == ".bar");
	testAssert(Path("../foo.tar.gz").extension() == ".gz");
	testAssert(Path("foo").extension() == "");
	testAssert(Path("/").extension() == "");
}

